5.1.2 node 常见面试题
1.Node 异步单线程原理
Node.js 的结构
Node.js 标准库,这部分是由 Javascript编写的,即我们使用过程中直接能调用的 API。在源码中的 lib 目录下可以看到。
Node bindings,这一层是 Javascript与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据。
实现在 node.cc 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。
V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript的关键,
它为 Javascript提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。
Libuv:它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。
C-ares:提供了异步处理 DNS 相关的能力。
http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。
2
3
4
5
6
7
8
9
10
11
12
13
与操作系统交互
举个简单的例子,我们想要打开一个文件,并进行一些操作,可以写下面这样一段代码:
var fs = require('fs');
fs.open('./test.txt', "w", function(err, fd) { //..do something});
2
这段代码的调用过程大致可描述为:lib/fs.js → src/node_file.cc → uv_fs
单线程
Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,
假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。
为什么一个单线程的效率可以这么高,同时处理数万级的并发而不会造成阻塞呢?就是我们下面所说的--------事件驱动。
事件驱动/事件循环
1、每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。
2、主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,
此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
3、主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,
从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。
主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,
此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。
当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
4、主线程不断重复上面的第三步。
2
3
4
5
6
7
8
9
我们所看到的node.js单线程只是一个js主线程,本质上的异步操作还是由线程池完成的,
node将所有的阻塞操作都交给了内部的线程池去实现,
本身只负责不断的往返调度,并没有进行真正的I/O操作, 从而实现异步非阻塞I/O,
这便是node单线程和事件驱动的精髓之处了。
libuv
Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,
libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,
事件循环机制也是它里面的实现
Event Loop的执行顺序:
HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加
每次事件循环都包含了6个阶段,对应到 libuv 源码中的实现
timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调
idle, prepare 阶段:仅node内部使用
poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
check 阶段:执行setImmediate()的回调
close callbacks 阶段:执行socket的close事件回调。
2
3
4
5
6
7
8
9
10
11
2.Node 如何多进程
3.co 模块原理
那么co就有两种实现方式,promise或者thunk。co4.0之前是用thunk实现的,之后是用promise实现的。
一种机制,在一个异步操作执行完毕以后通知下一个异步操作开始执行
4.node 的特点
- 异步 I/O
- 事件循环
- 单线程
- 跨平台
node 引入模块
优先从缓存加载
- 文件定位
- 路径分析
- 编译执行
epoll 是Linux内核为处理大批量文件描述符而作了改进的 poll
poll: 在一定时间内轮流看一遍里面有没有数据要读写
node 异步 I/O 模型基本要素
- 事件循环
- 观察者
- 请求对象
- I/O 线程池
node 非异步 I/O API
- setTimeout
- setInterval
- setImmediate
- process.nextTick()
node 异步编程解决方案
- 事件发布/订阅者模式
- Promise/Deferred 模式
- 流程控制库
++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++
1.什么是错误优先的回调函数?
错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。例如:
fs.readFile(filePath, function(err, data) {
if (err) {
//handle the error
}
// use the data object
});
2
3
4
5
6
2.如何避免回调地狱
你可以有如下几个方法:
模块化:将回调函数分割为独立的函数
使用Promises
使用yield
来计算生成器或Promise
2
3
4
3.如何用Node监听80端口
这题有陷阱!在类Unix系统中你不应该尝试去监听80端口,因为这需要超级用户权限。 因此不推荐让你的应用直接监听这个端口。
目前,如果你一定要让你的应用监听80端口的话,你可以有通过在Node应用的前方再增加一层反向代理 (例如nginx)来实现
4.什么是事件循环
Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式),
至少从Node.js开发者的角度是这样的。
而在底层,Node.js借助libuv来作为抽象封装层, 从而屏蔽不同操作系统的差异,
Node可以借助livuv来来实现多线程。
Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。
Node.js的运行机制如下。
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
2
3
4
5
6
7
8
9
核心概念
- Chrome V8 是 Google 发布的开源 JavaScript 引擎,采用 C/C++ 编写,在 Google 的 Chrome 浏览器中被使用。
Chrome V8 引擎可以独立运行,也可以用来嵌入到 C/C++ 应用程序中执行。 - Event Loop 事件循环(由 libuv 提供)
- Thread Pool 线程池(由 libuv 提供)
5.什么是Stub?举个使用场景
Stub是用于模拟一个组件或模块的函数或程序。在测试用例中, 简单的说,
你可以用Stub去模拟一个方法,从而避免调用真实的方法, 使用Stub你还可以返回虚构的结果。你可以配合断言使用Stub。
举个例子,在一个读取文件的场景中,当你不想读取一个真正的文件时:
单元测试
- 参考:Nodejs单元测试小结
单元测试根据主流的分类可以分成两类,分别是BDD和TDD
6.什么是测试金字塔?
测试金字塔指的是: 当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。